﻿using AutoMapper;
using IOA.MES.DataAccess.Entity;
using IOA.MES.DataAccess.Enum;
using IOA.MES.DataAccess.VModel;
using IOA.MES.Common;
using System;
using System.Collections.Generic;
using System.Data.Entity.Core.Common.CommandTrees;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IOA.Utils;
using IOA.MES.Log.DataAccess.Entity;
using IOA.MES.Log.Business;
using IOA.MES.Log.DataAccess.VModel;
using NLog;

namespace IOA.MES.Business
{
    /// <summary>
    /// 入库管理（错误代码：123001）
    /// </summary>
    public class InboundMgr
    {
        private Logger logger = LogManager.GetCurrentClassLogger();
        private MESContext mesDb = new MESContext("MES");

        /// <summary>
        /// 产品入库
        /// </summary>
        /// <param name="model">入库对象</param>
        /// <param name="products">产品列表</param>
        public void Inbound(VInbound model, List<VInboundProduct> products)
        {
            var packs = new List<Pack>();
            var packNos = products.Select(_ => _.BarCode).Where(_ => !string.IsNullOrWhiteSpace(_)).Select(_ => _.ToUpper().Trim()).Distinct().ToList();
            //入库包装校验
            if (model.InboundType == InboundType.ProductInbound)
            {
                var locations = products.Select(_ => _.Location).Distinct().ToList();
                var locationEntities = mesDb.WarehouseLocations.AsNoTracking().Where(_ => locations.Contains(_.Location)).ToList();
                if (locationEntities.Any(_ => !StringUtils.Equals(_.WarehouseCode, model.WarehouseCode)))
                {
                    var otherWarehouses = locationEntities.Where(_ => !StringUtils.Equals(_.WarehouseCode, model.WarehouseCode)).ToList();
                    throw new MultiLanBizException(123005, string.Join(", ", otherWarehouses.Select(_ => $"{_.Location}：{_.WarehouseName}")));//下列库位不在当前仓库下，请到首页切换仓库
                }
                packs = mesDb.Packs.AsNoTracking().Where(_ => packNos.Contains(_.PackNo) && _.PackStatus == PackStatus.Normal && _.Valid).ToList();
                if (packNos.Any(_ => !packs.Select(p => p.PackNo).Contains(_)))
                {
                    throw new MultiLanBizException(123001, string.Join("、", packNos.Where(_ => !packs.Select(p => p.PackNo).Contains(_)).Distinct().ToList()));// $"下列包装条码不存在，不可入库：{string.Join("、", packNos.Where(_ => !packs.Select(p => p.PackNo).Contains(_)).Distinct().ToList())}");
                }
                else if (packs.Any(_ => _.StockStatus == PackStockStatus.InStcok))
                {
                    throw new MultiLanBizException(123002, string.Join("、", packs.Where(_ => _.StockStatus == PackStockStatus.InStcok).Select(_ => _.PackNo).Distinct().ToList()));// $"下列包装已经在库，不可重复入库：{string.Join("、", packs.Where(_ => _.StockStatus == PackStockStatus.InStcok).Select(_ => _.PackNo).Distinct().ToList())}");
                }
                else if (packs.Any(_ => _.PackStatus != PackStatus.Normal))
                {
                    throw new MultiLanBizException(123003, string.Join("、", packs.Where(_ => _.PackStatus != PackStatus.Normal).Select(_ => _.PackNo).Distinct().ToList()));// $"下列包装已解包，不可入库：{string.Join("、", packs.Where(_ => _.PackStatus != PackStatus.Normal).Select(_ => _.PackNo).Distinct().ToList())}");
                }
                else if (packs.Any(_ => _.PackInspectStatus != PackInboundInspectStatus.Inspected))
                {
                    throw new MultiLanBizException(123004, string.Join("、", packs.Where(_ => _.PackInspectStatus != PackInboundInspectStatus.Inspected).Select(_ => _.PackNo).Distinct().ToList()));// $"下列包装没有通过入库检，不可入库：{string.Join("、", packs.Where(_ => _.PackInspectStatus != PackInboundInspectStatus.Inspected).Select(_ => _.PackNo).Distinct().ToList())}");
                }

                packs.ForEach(_ =>
                {
                    mesDb.Packs.Attach(_);
                    _.StockStatus = PackStockStatus.InStcok;
                    _.WarehouseCode = model.WarehouseCode;
                    _.WarehouseName = model.WarehouseName;
                    _.AreaCode = locationEntities.FirstOrDefault(l => StringUtils.Equals(l.Location, products.FirstOrDefault(p => StringUtils.Equals(p.BarCode, _.PackNo))?.Location))?.AreaCode;
                    _.AreaName = locationEntities.FirstOrDefault(l => StringUtils.Equals(l.Location, products.FirstOrDefault(p => StringUtils.Equals(p.BarCode, _.PackNo))?.Location))?.AreaName;
                    _.Location = products.FirstOrDefault(p => StringUtils.Equals(p.BarCode, _.PackNo))?.Location;
                    _.ModifyBy = model.CreateBy;
                    _.ModifyTime = DateTime.Now;
                });
                var packProducts = (from pack in mesDb.Packs.AsNoTracking()
                                    join packProduct in mesDb.PackProducts.AsNoTracking() on pack.PackNo equals packProduct.PackNo
                                    where packNos.Contains(pack.PackNo) && pack.Valid && pack.PackStatus == PackStatus.Normal && pack.PackInspectStatus == PackInboundInspectStatus.Inspected
                                          && packProduct.Valid
                                    select new
                                    {
                                        pack.PackNo,
                                        packProduct.ProductCode,
                                        packProduct.ProductName,
                                        pack.ProductSpecs
                                    }).ToList();

                products.ForEach(product =>
                {
                    product.WarehouseCode = model.WarehouseCode;
                    product.WarehouseName = model.WarehouseName;
                    product.AreaCode = locationEntities.FirstOrDefault(l => string.Equals(l.Location, product.Location, StringComparison.CurrentCultureIgnoreCase))?.AreaCode;
                    product.AreaName = locationEntities.FirstOrDefault(l => string.Equals(l.Location, product.Location, StringComparison.CurrentCultureIgnoreCase))?.AreaName;
                    //按包装入库，BarCode就是打包编号
                    product.ProductCode = packProducts.FirstOrDefault(_ => string.Equals(product.BarCode, _.PackNo, StringComparison.CurrentCultureIgnoreCase))?.ProductCode;
                    product.ProductName = packProducts.FirstOrDefault(_ => string.Equals(product.BarCode, _.PackNo, StringComparison.CurrentCultureIgnoreCase))?.ProductName;
                    product.ProductSpecs = packProducts.FirstOrDefault(_ => string.Equals(product.BarCode, _.PackNo, StringComparison.CurrentCultureIgnoreCase))?.ProductSpecs;
                });
            }

            model.CreateTime = DateTime.Now;
            model.InboundNo = DateTime.Now.ToString("yyMMdd");
            var inbound = Mapper.Map<Inbound>(model);
            mesDb.Inbounds.Add(inbound);
            mesDb.SaveChanges();
            inbound.InboundNo = $"{inbound.InboundNo}{(inbound.PkId % 100000):00000}";
            mesDb.SaveChanges();

            products.ForEach(product =>
            {
                product.InboundNo = inbound.InboundNo;
                product.CreateTime = DateTime.Now;
            });
            mesDb.InboundProducts.AddRange(Mapper.Map<List<InboundProduct>>(products));
            mesDb.SaveChanges();

            new TaskFactory().StartNew(() =>
            {
                try
                {
                    //调拨入库修改实收数量
                    if (model.InboundType == InboundType.AllocateInbound)
                    {
                        #region 修改实收数量
                        var allocateProducts = mesDb.AllocateProducts.AsNoTracking().Where(_ => _.AllocateNo == model.InboundTypeNo && _.Valid).ToList();
                        foreach (var item in allocateProducts)
                        {
                            var curReceiveProducts = products.Where(_ => StringUtils.Equals(_.ProductCode, item.ProductCode) && StringUtils.Equals(_.BatchNo, item.BatchNo)).ToList();
                            mesDb.AllocateProducts.Attach(item);
                            item.ReceiveQuantity = item.ReceiveQuantity + curReceiveProducts.Sum(_ => _.ReceiveQuantity);
                        }
                        mesDb.SaveChanges();
                        #endregion
                    }

                    #region 进入库位
                    if (model.InboundType == InboundType.AllocateInbound)
                    {
                        //线边仓收货时，查看是否有已有库存记录，有的话直接加数量
                        var storages = mesDb.Storages.AsNoTracking().Where(_ => _.WarehouseCode == model.WarehouseCode && _.Valid).ToList();
                        foreach (var storage in storages)
                        {
                            var curProducts = products.Where(_ => StringUtils.Equals(_.BatchNo, storage.PackBatchNo) && StringUtils.Equals(_.ProductCode, storage.ProductCode)).ToList();
                            if (curProducts.Any())
                            {
                                storage.Quantity = storage.Quantity + curProducts.Sum(_ => _.ReceiveQuantity);
                                curProducts.ForEach(_ =>
                                {
                                    products.Remove(_);
                                });
                            }
                        }
                    }

                    var productCodes = products.Select(_ => _.ProductCode).Distinct().ToList();
                    var productInfos = mesDb.Products.AsNoTracking().Where(_ => productCodes.Contains(_.ProductCode) && _.Valid).ToList();
                    var allocate = model.InboundType == InboundType.ProductInbound ? null : mesDb.AllocateOrders.AsNoTracking().FirstOrDefault(_ => _.AllocateNo == model.InboundTypeNo && _.Valid);
                    mesDb.Storages.AddRange(products.Select(_ => new Storage
                    {
                        WorkOrderNo = model.InboundType == InboundType.AllocateInbound ? allocate?.WorkOrderNo : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.WorkOrderNo,
                        OrderNo = model.InboundType == InboundType.AllocateInbound ? allocate?.OrderNo : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.OrderNo,
                        CategoryCode = productInfos.FirstOrDefault(p => StringUtils.Equals(p.ProductCode, _.ProductCode))?.CategoryCode,
                        CategoryName = productInfos.FirstOrDefault(p => StringUtils.Equals(p.ProductCode, _.ProductCode))?.CategoryName,
                        ProductCode = _.ProductCode,
                        ProductName = _.ProductName,
                        ProductSpecs = _.ProductSpecs,
                        CreateBy = model.CreateBy,
                        CreateTime = DateTime.Now,
                        InventoryType = model.InboundType == InboundType.AllocateInbound ? InventoryType.Mterial : InventoryType.ProductPack,
                        PackNo = model.InboundType == InboundType.AllocateInbound ? string.Empty : _.BarCode,
                        PackBatchNo = model.InboundType == InboundType.AllocateInbound ? _.BatchNo : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.PackBatchNo,
                        CabinetNo = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.CabinetNo,
                        Quantity = _.ReceiveQuantity,
                        DetailQuantity = model.InboundType == InboundType.AllocateInbound ? _.ReceiveQuantity : (packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.Quantity ?? 0),
                        WarehouseCode = model.WarehouseCode,
                        WarehouseName = model.WarehouseName,
                        AreaCode = _.AreaCode,
                        AreaName = _.AreaName,
                        Location = _.Location,
                        CustomerCode = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.CustomerCode,
                        CustomerName = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.CustomerName,
                        WorkshopCode = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.WorkshopCode,
                        WorkshopName = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.WorkshopName,
                        ComponentColor = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.ComponentColor,
                        QLevel = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.QLevel,
                        PowerLevel = model.InboundType == InboundType.AllocateInbound ? null : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.IVPowerLevel,
                        CurrentLevel = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.IVCurrentLevel,
                        NameplateModel = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.NameplateModel,
                        BatteryFactory = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.BatteryFactory,
                        BatterySpecs = model.InboundType == InboundType.AllocateInbound ? string.Empty : packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.BarCode))?.BatterySpecs
                    }));
                    mesDb.SaveChanges();
                    #endregion

                    #region 修改包装所在库位
                    if (model.InboundType == InboundType.ProductInbound)
                    {
                        var components = mesDb.Components.AsNoTracking().Where(_ => packNos.Contains(_.PackNo) && _.Valid).ToList();
                        components.ForEach(_ =>
                        {
                            mesDb.Components.Attach(_);
                            _.WarehouseCode = packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.PackNo))?.WarehouseCode;
                            _.WarehouseName = packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.PackNo))?.WarehouseName;
                            _.WarehouseAreaCode = packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.PackNo))?.AreaCode;
                            _.WarehouseAreaName = packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.PackNo))?.AreaName;
                            _.Location = packs.FirstOrDefault(p => StringUtils.Equals(p.PackNo, _.PackNo))?.Location;
                        });
                        mesDb.SaveChanges();

                        var users = new UserMgr().QueryUsers(new List<int> { model.CreateBy });
                        var logs = components.Select(_ => new VOprLog
                        {
                            SubjectType = "组件",
                            SubjectNo = _.BarCode,
                            LogName = "组件入库",
                            LogStatus = "正常",
                            OpeatorId = model.CreateBy,
                            OpeatorName = users.FirstOrDefault()?.TrueName,
                            LogContent = $"组件入库，入库单号：{inbound.InboundNo}，仓库：{_.WarehouseAreaName}，库区：{_.WarehouseAreaName}，库位：{_.Location}",
                            LogTime = DateTime.Now,
                            CreateBy = model.CreateBy
                        }).ToList();
                        new OprLogMgr().AddLogs(logs);
                    }
                    #endregion
                }
                catch (Exception ex)
                {
                    logger.Fatal(ex, $"入库异常，入库单号：{inbound.InboundNo}，异常信息：{ex.Message}");
                }
            });
        }

        /// <summary>
        /// 获取入库单对象
        /// </summary>
        /// <param name="inboundNo">入库单号</param>
        /// <returns>入库单对象</returns>
        public VInbound GetInbound(string inboundNo)
        {
            var entity = mesDb.Inbounds.AsNoTracking().FirstOrDefault(_ => _.InboundNo == inboundNo && _.Valid);
            var model = Mapper.Map<VInbound>(entity);
            if (model != null)
            {
                var inboundProducts = mesDb.InboundProducts.AsNoTracking().Where(_ => _.InboundNo == inboundNo && _.Valid).ToList();
                model.Products = Mapper.Map<List<VInboundProduct>>(inboundProducts);
                var user = model.CreateBy <= 0 ? null : new UserMgr().GetUser(model.CreateBy);
                model.CNCreateBy = user == null ? string.Empty : user.TrueName;

                var packNos = inboundProducts.Select(_ => _.BarCode).Distinct().ToList();
                var packs = mesDb.Packs.AsNoTracking().Where(_ => packNos.Contains(_.PackNo) && _.PackStatus == PackStatus.Normal && _.Valid).ToList();
                packs.ForEach(pack =>
                {
                    pack.WarehouseCode = inboundProducts.FirstOrDefault(_ => StringUtils.Equals(_.BarCode, pack.PackNo))?.WarehouseCode;
                    pack.WarehouseName = inboundProducts.FirstOrDefault(_ => StringUtils.Equals(_.BarCode, pack.PackNo))?.WarehouseName;
                    pack.AreaCode = inboundProducts.FirstOrDefault(_ => StringUtils.Equals(_.BarCode, pack.PackNo))?.AreaCode;
                    pack.AreaName = inboundProducts.FirstOrDefault(_ => StringUtils.Equals(_.BarCode, pack.PackNo))?.AreaName;
                    pack.Location = inboundProducts.FirstOrDefault(_ => StringUtils.Equals(_.BarCode, pack.PackNo))?.Location;
                });
                model.Packs = Mapper.Map<List<VPack>>(packs);
            }
            return model;
        }

        /// <summary>
        /// 查询入库单列表
        /// </summary>
        /// <param name="page">页面索引</param>
        /// <param name="limit">页面容量</param>
        /// <param name="warehouseCode">仓库代码</param>
        /// <param name="inboundType">入库类型</param>
        /// <param name="startDate">开始日期</param>
        /// <param name="endDate">截止日期</param>
        /// <param name="inboundBy">入库人</param>
        /// <returns>入库单列表</returns>
        public List<VInbound> QueryInbounds(int page, int limit, out int total, string warehouseCode, InboundType inboundType, DateTime? startDate, DateTime? endDate, int inboundBy = 0)
        {
            endDate = !endDate.HasValue ? endDate : endDate.Value.AddDays(1);
            var query = from task in mesDb.Inbounds.Where(_ => _.InboundType == inboundType && _.Valid)
                        select task;
            if (startDate.HasValue)
            {
                query = query.Where(_ => _.CreateTime >= startDate.Value);
            }
            if (endDate.HasValue)
            {
                query = query.Where(_ => _.CreateTime < endDate);
            }
            if (!string.IsNullOrWhiteSpace(warehouseCode))
            {
                query = query.Where(_ => _.WarehouseCode == warehouseCode);
            }
            if (inboundBy > 0)
            {
                query = query.Where(_ => _.CreateBy == inboundBy);
            }

            total = query.Count();
            var entities = query.OrderByDescending(_ => _.PkId).Skip((page - 1) * limit).Take(limit);
            var models = Mapper.Map<List<VInbound>>(entities);
            var userIds = models.Select(_ => _.CreateBy).Distinct().ToList();
            var users = new UserMgr().QueryUsers(userIds);
            var inboundNos = models.Select(_ => _.InboundNo).Distinct().ToList();
            var inboundProducts = mesDb.InboundProducts.Where(_ => inboundNos.Contains(_.InboundNo) && _.Valid).ToList();
            models.ForEach(task =>
            {
                task.CNCreateBy = users.FirstOrDefault(_ => _.PkId == task.CreateBy)?.TrueName;
                task.Products = Mapper.Map<List<VInboundProduct>>(inboundProducts.Where(_ => string.Equals(_.InboundNo, task.InboundNo, StringComparison.CurrentCultureIgnoreCase)).ToList());
            });
            return models;
        }
    }
}
